﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;


namespace EnumUtils
{
  public static class FlagEnumUtilities
  {
    public static BitArray ConvertEnumToBitArray( Enum flagEnum ) {
      Contract.Requires<ArgumentNullException>(flagEnum != default(Enum));

      Type enumFlagsUnderlyingType = Enum.GetUnderlyingType(flagEnum.GetType());
      dynamic enumFlagsBits_integer = Convert.ChangeType(flagEnum, enumFlagsUnderlyingType);
      BitArray enumFlagsBits = new BitArray(BitConverter.GetBytes(enumFlagsBits_integer));
      enumFlagsBits.Length = (int)Math.Ceiling(Math.Log(GenerateBitmask(flagEnum), 2));
      
      return enumFlagsBits;
    }

    public static Enum 
    ConvertIntToEnum( Type typeofEnum, int enumAsAnInt ) {
      Contract.Requires<ArgumentNullException>(typeofEnum != default(Type));

      return (Enum)Enum.Parse(typeofEnum, enumAsAnInt.ToString());
    }



    [Pure]
    public static bool
    DoesEnumHaveFlagsAttribute( Enum flagEnum ) {
      Contract.Requires<ArgumentNullException>(flagEnum != default(Enum));
      bool hasFlagsAttribute = flagEnum.GetType().GetCustomAttributes(false).Any(flag => flag.GetType() == typeof(FlagsAttribute));
      return hasFlagsAttribute;
    }


    /// <summary>
    /// Performs an operation on each flag of a flags enumeration.
    /// </summary>
    /// 
    /// <param name="flagEnum">An enum with a Flags attribute to process.</param>
    /// 
    /// <param name="operation">An operation to execute on the passed enumeration. Must not attempt to do operations that you can't do to an enumeration (like bit-shifting).</param>
    public static void
    ForeachEnumFlag( Enum flagEnum, Action<Enum> operation ) {
      Contract.Requires<ArgumentNullException>(flagEnum != default(Enum));
      Contract.Requires<ArgumentNullException>(operation != default(Action<Enum>));
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);

      BitArray enumFlagsBits = ConvertEnumToBitArray(flagEnum);
      for (int i = enumFlagsBits.Count - 1; i >= 0; --i) {
        ulong enumAsInteger = Convert.ToUInt64(enumFlagsBits[i]) << i;
        Enum individualEnumFlag = (Enum)Enum.Parse(flagEnum.GetType(), enumAsInteger.ToString());
        if (IsFlagDefined(individualEnumFlag)) {
          operation(individualEnumFlag);
        }
      }
    }



    /// <summary>
    /// Tests to determine if an enumeration flag is explicitly defined for an enumeration.
    /// </summary>
    /// 
    /// <param name="flagToTest"></param>
    /// 
    /// <param name="countNoneFlagAsFlag">
    /// Determines whether the None flag should count as a flag being 
    /// defined for the enum. Defaults to false.
    /// </param>
    /// 
    /// <returns></returns>
    /// 
    /// <remarks>
    /// Code based off of a code snippet found on pp. 95 in "C# 3.0 in a 
    /// Nutshell", written by Albahari, J &amp; Albahari, B.
    /// </remarks>
    public static bool
    IsFlagDefined( Enum flagToTest, bool countNoneFlagAsFlag = false ) {
      Contract.Requires<ArgumentNullException>(flagToTest != default(Enum));
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagToTest), ErrorMessageUtils.EnumNotAFlagsEnumMessage);


      int dummyInt;
      if (int.TryParse(flagToTest.ToString(), out dummyInt)) {
        return false;
      }
      else {
        switch (flagToTest.ToString()) {
          case "None":
            return countNoneFlagAsFlag ? true : false;
          default:
              // Assume that any other value represents a valid flag
            return true;
        }
      }
    }


    public static int 
    GenerateBitmask( Enum flagEnum ) {
      Contract.Requires<ArgumentNullException>(flagEnum != default(Enum));
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum));

      var possibleEnumValues = Enum.GetValues(flagEnum.GetType());
      dynamic maxEnumValue = Convert.ChangeType(possibleEnumValues.GetValue(possibleEnumValues.GetUpperBound(0)), Enum.GetUnderlyingType(flagEnum.GetType()));
      dynamic intToEnumBitmask = 1;
      // This is the value of 2^0

      while (intToEnumBitmask <= maxEnumValue) {
        intToEnumBitmask <<= 1;
      }
      --intToEnumBitmask;


      return intToEnumBitmask;
    }



    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// 
    /// <param name="flagEnum"></param>
    /// 
    /// <returns></returns>
    public static IEnumerator
    GetEnumerator( Enum flagEnum ) {
      Contract.Requires<ArgumentNullException>(flagEnum != default(Enum));

      return GetGenericEnumerator(flagEnum);
    }


    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// 
    /// <param name="flagEnum"></param>
    /// 
    /// <returns></returns>
    public static IEnumerator<Enum>
    GetGenericEnumerator( Enum flagEnum ) {
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);


      BitArray enumBits = ConvertEnumToBitArray(flagEnum);

      foreach (bool flag in enumBits) {
        Enum enumFlag = (Enum)Enum.Parse(flagEnum.GetType(), Convert.ToInt32(flag).ToString());
        if (IsFlagDefined(enumFlag)) {
          yield return enumFlag;
        }
      }

    }



    #region Helper Methods
    #endregion
  }
}
